<?php
/**
 * PM5 PHP Framework
 * Originally developed by Patrick McGovern
 * 
 * This is the main class that runs the entire application.
 * It is instantiated from /public/index.php
 * 
 * All files and classes that are required for the application 
 * to run are included & instantiated here.
 *
 * For a better understanding of the flow of data, read through
 * the __construct function contents.
 * 
 * This file should never be modified.
 * 
 * If you want to have a custom class instantiated during the
 * processing of the application before the content, action
 * or output handlers are run, save the class in the
 * functionality/ directory and set up it's instantiation
 * in config/constructExtender.php
 * This allows the developer (you) to have more control over 
 * how your application functions.
 */
class Main
{
	/**
	 * Cleaned parameters passed via GET or POST
	 *
	 * @var object
	 */
	private $_passed;
	
	/**
	 * Object containing core functions available application-wide
	 *
	 * @var object
	 */
	public $coreFunctions;
	
	/**
	 * Output type to use
	 * example: 'standardBrowser' or 'mobileBrowser'
	 *
	 * @var string
	 */
	public $outputType;
	
	/**
	 * Handler type 
	 * 'content' or 'action'
	 * Content handlers generate output
	 * Actions handlers commit an action and redirect elsewhere or die
	 * upon completion
	 *
	 * @var string
	 */
	public $handlerType;
	
	/**
	 * This is an object containing either the action or content 
	 * handler
	 * 
	 * @var object
	 */
	public $handler;
	
	/**
	 * This is an object containing the determined output handler
	 *
	 * @var object
	 */
	private $_outputHandler;
	
	/**
	 * Instantiate all necessary elements of the application
	 *
	 * @return boolean
	 */
	public function __construct()
	{
		/**
		 * Include core configuration settings
		 */
		include_once 'config/core.config.php';
		
		/**
		 * Set the default timezone for the application
		 */
		date_default_timezone_set(DEFAULT_DATE_TIMEZONE);

		/**
		 * Code profiling class
		 */
		include_once "functionality/profiler.php";
		$this->profiler = new Profiler($this);
		$this->profiler->logAll(__FILE__, __LINE__,'Main class after instantiation of profiler class');
		
		/**
		 * Clean all passed parameters and place into class variables
		 */
		include_once 'functionality/handlePassedParams.php';
		$this->_passed = new HandlePassedParams($this);
		
		/**
		 * Bring in the core functionality
		 */
		include_once '../functionality/core.functions.php';
		$this->CoreFunctions = new CoreFunctions($this);
		
		/**
		 * Create a connection to the database and make it available
		 * to the application via a class object
		 */
		/*
		include_once '../functionality/database.php';
		$this->db = new DB($this);
		*/
		
		/**
		 * The construct extender allows developers to extend the functionality of
		 * this class without modifying this class, making future upgrades of this
		 * framework easier to install
		 */
		include_once '../config/constructExtender.php';
		$this->_constructExtender = new ConstructExtender($this);
		
		/**
		 * Determine output type
		 */
		$this->_determineOutputType();
		
		/**
		 * Determine handler type
		 * (content handler / action handler)
		 */
		$this->_determineHandler();
		
		/**
		 * Run selected handler
		 */
		$this->_runHandler();
		
		/**
		 * Check for the existence of expected values for the output handler.
		 * If they do not exist, set to defaults as defined within 
		 * config/core.config.php
		 */
		$this->_setExpectedOutputHandlerValues();
		
		/**
		 * Run output handler
		 * This generates the wrapper HTML & CSS for the application
		 * Nothing is processed for action handlers
		 */
		$this->_runOutputHandler();
		
		/**
		 * Tie all output together
		 */
		$this->_assembleOutput();
		
		/**
		 * GZip compress output
		 */
		$this->_compressOutput();
		
		/**
		 * Compress & send output to the browser
		 */
		$this->_presentOutput();
		
		return true;
	}
	
	/**
	 * Based on passed parameters, session variables and mobile
	 * device detection, determine what type of output we are
	 * presenting to the user
	 *
	 * @return boolean
	 */
	private function _determineOutputType()
	{
		/**
		 * If a request was sent to specify & override the 
		 * output type, set it as a session variable for
		 * later use
		 */
		if (!empty($this->passed->outputType)){
			switch($this->passed->outputType){
				case 'standardBrowser':
					$this->outputType = 'standardBrowser';
					break;
				case 'mobileBrowser':
					$this->outputType = 'mobileBrowser';
					break;
				case 'ajax':
					$this->outputType = 'ajax';
					break;
				default:
					$this->outputType = 'standardBrowser';
					break;
			}
			
			$_SESSION['environment']['outputType'] = $this->outputType;
			return true;
		}
		
		/**
		 * Check to see if the user is connecting from a mobile browser
		 */
		if ($this->functions->isMobileBrowser() || (!empty($_SESSION['environment']['outputType']) && $_SESSION['environment']['outputType'] == 'mobileBrowser')){
			$this->outputType = 'mobileBrowser';
			
			return true;
		}
		
		/**
		 * Check to see if this is an ajax request
		 * These requests are usually modal windows & background content
		 * requests
		 */
		if (!empty($_SESSION['environment']['outputType']) && $_SESSION['environment']['outputType'] == 'ajax'){
			$this->outputType = 'ajax';
			
			return true;
		}
		
		/**
		 * if nothitng has been set or detected by this point, we 
		 * default to the standard browser output
		 */
		$this->outputType = 'standardBrowser';
		
		return true;
	}
	
	/**
	 * Based on passed parameters, determine which type of handler
	 * to run and determine if the requested handler exists
	 *
	 * @return boolean
	 */
	private function _determineHandler()
	{
		if (empty($this->passed->view) && empty($this->passed->action)){
			$this->handlerType = 'content';
			$this->passed->view = 'home';
			
			return true;
		}
		elseif (!empty($this->passed->view)){
			$this->handlerType = 'content';
			
			/**
			 * Do not allow a different directory to be specified
			 */
			$this->passed->view = str_replace('../','',$this->passed->view);
			
			/**
			 * Check to ensure the requested content handler exists
			 * If not, default to home
			 */
			if (!is_file(APPLICATION_ROOT_FILEPATH . "applicationIncludes/contentHandlers/{$this->passed->view}.php")){
				$this->passed->view = 'home';
			}
			
			return true;
		}
		else {
			/**
			 * We are dealing with an action handler
			 * Check to ensure the requested action handler exists
			 * If not, change set output type to view and default
			 * to home
			 */
			$this->passed->action = str_replace('../','',$this->passed->action);
			
			if (!is_file(APPLICATION_ROOT_FILEPATH . "applicationIncludes/actionHandlers/{$this->passed->action}.php")){
				$this->handlerType = 'content';
				$this->passed->view = 'home';
				
				return true;
			}
			else {
				$this->handlerType = 'action';
				
				return true;
			}
		}
	}
	
	/**
	 * Include the selected handler and instantiate it
	 *
	 * @return boolean
	 */
	private function _runHandler()
	{
		include_once "{$this->handlerType}Handlers/{$this->passed->view}.php";
		$this->handler = new Handler($this);
		
		return true;
	}
	
	/**
	 * Check for the existence of expected values for the output handler.
	 * If they do not exist, set to defaults as defined within 
	 * config/core.config.php
	 * 
	 * This only needs to be done for content handlers
	 *
	 * @return boolean
	 */
	private function _setExpectedOutputHandlerValues()
	{
		if ($this->handlerType == 'content'){
			$this->pageTitle		= (!empty($this->handler->pageTitle))		? $this->handler->pageTitle			: DEFAULT_PAGE_TITLE;
			$this->metaKeywords		= (!empty($this->handler->metaKeywords))	? $this->handler->metaKeywords		: DEFAULT_META_KEYWORDS;
			$this->metaDescription	= (!empty($this->handler->metaDescription))	? $this->handler->metaDescription	: DEFAULT_META_DESCRIPTION;
		}
		
		return true;
	}
	
	/**
	 * Run the selected output handler
	 * This operation should only be run of the handlerType is 
	 * 'content'
	 *
	 * @return boolean
	 */
	private function _runOutputHandler()
	{
		if ($this->handlerType == 'content'){
			include_once "output/{$this->outputType}.php";
			$this->_outputHandler = new OutputHandler($this);
		}
		
		return true;
	}
	
	/**
	 * Assemble all required HTML
	 *
	 * @return boolean
	 */
	private function _assembleOutput()
	{
		$html  = ($this->handlerType == 'content') ? $this->_outputHandler->headerHTML : '';
		$html .= $this->handler->HTML;
		$html .= ($this->handlerType == 'content') ? $this->_outputHandler->footerHTML : '';
		
		$this->_outputHTML = $html;
		unset($html);

		return true;
	}
	
	/**
	 * Compress generated HTML and pass to the browser
	 * If the application is in debug mode (as defined in 
	 * config.core.config.php), the output is not compressed.
	 *
	 * @return boolean
	 */
	private function _presentOutput()
	{
		/**
		 * Start output buffering
		 * This allows us to call a callback function to affect all output
		 * HTML as we wish.
		 */
		if (DEBUG === false){
			ob_start(array($this, '_outputBufferingCallback'));
		}
		
		/**
		 * Add the generated HTML to the output
		 */
		echo $this->_outputHTML;
		
		/**
		 * include the output from the profiler class
		 */
		if (DEBUG === TRUE){
			$this->profiler->output();
		}
		
		/**
		 * Flush the output to the browser
		 */
		if (DEBUG_MODE === false){
			ob_end_flush();
		}
		
		return true;
	}
	
	/**
	 * This is the callback function that post-processes all finalized
	 * generated HTML.
	 * Here we remove repeated spaces and compress the HTML with gzip
	 *
	 * @param string $buffer
	 */
	private function _outputBufferingCallback($buffer)
	{
		/**
		 * Reove repeated spaces
		 */
		$buffer = trim(preg_replace('/\s+/', ' ', $buffer));

		/**
		 * Compress HTML with gzip
		 */
		/**
		 * Ensure the browser accepts gzip compression
		 */
		if(strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE){
			$buffer = gzencode($buffer);
			header('Content-Encoding: gzip');
		}

		return $buffer;
	}
}